# 轨道控制单例
import threading

from PyQt5.QtCore import QRunnable, pyqtSlot, QTimer, QObject, pyqtSignal, QThread, QTime

import utils
from Boiler import Boiler
from ConveyorBelt import ConveyorManager
from singleton import singleton
import serial
import modbus_tk
import modbus_tk.defines as cst
from modbus_tk import modbus_rtu
from modbus_tk.exceptions import *
from serial.serialutil import SerialBase, SerialException, to_bytes, portNotOpenError, writeTimeoutError
from time import sleep
from ModbusManager import ModbusManager

from WorkSignals import WorkerSignals
import traceback, sys
from loguru import logger


# class Worker(QRunnable):
#     def __init__(self, fn, *args, **kwargs):
#         super(Worker, self).__init__()
#
#         # Store constructor arguments (re-used for processing)
#         self.fn = fn
#         self.args = args
#         self.kwargs = kwargs
#         self.signals = WorkerSignals()
#         self.kwargs['progress_callback'] = self.signals.progress
#
#     @pyqtSlot()
#     def run(self):
#         '''
#         Initialise the runner function with passed args, kwargs.
#         '''
#
#         # Retrieve args/kwargs here; and fire processing using them
#         try:
#             result = self.fn(*self.args, **self.kwargs)
#         except:
#             traceback.print_exc()
#             exctype, value = sys.exc_info()[:2]
#             self.signals.error.emit((exctype, value, traceback.format_exc()))
#         else:
#             self.signals.result.emit(result)  # Return the result of the processing
#         finally:
#             self.signals.finished.emit()  # Done


@singleton
class Track(QObject):
    _read_lock = threading.Lock()
    # master = None
    dev_id = 3
    interrupt_flag = False

    position = 0

    pos_signal = pyqtSignal(object)

    def __init__(self):
        super().__init__()
        self.__position = 0
        # self.threadpool = QThreadPool()
        # self.threadpool.setMaxThreadCount(2)

        # self.timer = QTimer()
        # self.timer.setInterval(1000)
        # self.timer.timeout.connect(self.update)
        # self.timer.start()

        self.target_pos = 0

    def home(self):
        try:
            # with Track._read_lock:
            # 0：非复位状态 1：取消复位 2：SQ1 复位 3：SQ2 复位 4：测量行程
            while True:
                ret = ModbusManager().execute(self.dev_id, cst.WRITE_SINGLE_REGISTER, 0xaa, output_value=3)  # SQ2复位
                if ret:
                    break
            sleep(0.1)
            # print("_read_lock")
            count = 15  # 时间限制
            while True:
                # 0 ：未完成 1 ：完成
                # ret_position = ModbusManager().execute(self.dev_id, cst.READ_HOLDING_REGISTERS, 0x23, 1)  # 读是否完成
                ret_sq2_lev = ModbusManager().execute(self.dev_id, cst.READ_HOLDING_REGISTERS, 0x2d, 1)  # 检测电平
                # logger.info("{} {}".format(ret_position, ret_sq2_lev))
                # 移动完成 & 限位开关电平为低
                # if ret_position[0] == 1 and ret_sq2_lev[0] == 1:
                #     return True

                pos = self.get_pos()
                logger.info("{} {}".format(pos, 1))
                if utils.approximate(pos, 0, 10) and ret_sq2_lev[0] == 1:
                    return True

                count = count - 1
                if count == 0:
                    return False
                sleep(1)

        except ModbusInvalidResponseError as e:
            print("home ModbusInvalidResponseError {}".format(e))
            return False
        except BaseException as e:
            print("home BaseException {}".format(e))
            return False

    def move_to(self, params):
        try:
            # print(self.master)
            self.target_pos = int(params)
            with Track._read_lock:
                ModbusManager().execute(self.dev_id, cst.WRITE_MULTIPLE_REGISTERS, 0x44,
                                        output_value=[500, 0, 0, self.target_pos])
        except ModbusInvalidResponseError as e:
            print("move_to ModbusInvalidResponseError {}".format(e))
            return False
        except BaseException as e:
            # todo any exception
            print("move_to BaseException {}".format(e))
            # self.init()
        pass

    def wait_for_arrival(self):
        pass

    @property
    def position(self):
        return self.__position

    @position.setter
    def position(self, position):
        if isinstance(position, int):
            self.__position = position
        else:
            print("error not int")

    def interrupt(self, flag):
        # print("interrupt = {}".format(flag))
        self.interrupt_flag = flag

    def is_interrupted(self):
        return self.interrupt_flag

    def get_position(self, progress_callback):
        # print("update")
        # self.position = self.position + 1
        # return self.position
        return self.get_pos()

    # def get_position_result(self, pos):
    #     self.pos_signal.emit(pos)

    # def update(self):
    #     worker = Worker(self.get_position)  # Any other args, kwargs are passed to the run function
    #     worker.signals.result.connect(self.get_position_result)
    #     self.threadpool.start(worker)
    # worker.signals.finished.connect(self.thread_complete)
    # worker.signals.progress.connect(self.progress_read_status_fn)

    def get_pos(self):
        with Track._read_lock:
            try:
                ret = ModbusManager().execute(self.dev_id, cst.READ_HOLDING_REGISTERS, 0x25, 1)
            except ModbusInvalidResponseError as e:
                print("reach ModbusInvalidResponseError {}".format(e))
                return 0
            except BaseException as e:
                # todo any exception
                print("move_to BaseException {}".format(e))
                # self.init()
                return 0
            else:
                # logger.debug(ret)
                return int(ret[0])

    def reach(self):
        return utils.approximate(self.get_pos(), self.target_pos, 10)
        # return self.get_pos() == self.target_pos
        # return False

    def do_action(self, params, action):
        self.interrupt(False)
        pos = params["pos"]
        if "find_bottle_index" in params:
            pos = ConveyorManager().find_track_pos_with_bottle()  # 找到有瓶子传送带对应的轨道位置

        if "find_hold_index" in params:
            pos = Boiler().find_pos_by_hold(action.get_hold_index())  # 找到有瓶子传送带对应的轨道位置

        self.move_to(pos)
        return True

    def wait_action(self, params=None):
        # logger.info(params)
        timeout = QTime.currentTime().addSecs(params["wait_timeout"])
        while not self.reach():
            if self.is_interrupted():
                return False
            sleep(0.5)
            if QTime.currentTime() > timeout:
                logger.error("Track Move Timeout!")
                return False

        return True
